﻿using System;
using System.Collections.Generic;

namespace BizElements.TemplateEngine
{
    #region Documentation.
    /// <summary>Defines properties that may be used in XML template expressions (delimited by $ signs).</summary>
    /// <remarks><para>Expressions, i.e. code segments delimited by $ signs in XML templates, are compiled as classes which implement <b>ITemplateExpression</b> interface.</para>
    /// <para>C# template expressions have access to the <see cref="Input"/> and <see cref="Variables"/> public properties and
    /// a collection of Generate* and Write* method.</para>
    /// <para>All public types in the System, BizElements.Enteprise may be used in C# template expressions as well as
    /// assemblies explicitely referenced in <b>ReferencedAssemblies</b> element of template's XML file.</para></remarks>
    #endregion
    public interface ITemplateExpression
    {
        #region Properties available for usage in XML templates.

        /// <summary>Input component.</summary>
        ITemplateInputComponent Input { get; }

        /// <summary>Template variables.</summary>
        TemplateVariableCollection Variables { get; }

        /// <summary>Input component variables/metadata.</summary>
        ComponentVariableSet ComponentVariables { get; }

        #endregion

        #region Generate* and Write* methods available for usage in XML templates.

        /// <summary>Generates the specified template segment for the given input components.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="components">A collection of input components.</param>
        /// <returns>Generated text. Empty string if <b>components</b> collection is empty.</returns>
        string Generate(string segmentId, IEnumerable<ITemplateInputComponent> components);

        /// <summary>Generates the specified template segment for input components that match the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        string Generate(string segmentId, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria);

        /// <summary>Generates the specified template segment for input component if it matches the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        string GenerateIf(string segmentId, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria);

        /// <summary>Generates the specified template segment for input component if it matches the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="evaluatedComponentCriteria">An array of flags. All must be <b>true</b> to generate the segment.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        string GenerateIf(string segmentId, ITemplateInputComponent component, params bool[] evaluatedComponentCriteria);

        /// <summary>Generates the specified template segment for the given input components if the specified condition is met.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="components">A collection of input components.</param>
        /// <param name="criteriaParameter">A component which is to be passed to the supplied criteria methods.</param>
        /// <param name="criteria">An array of predicates that define a set of criteria for the component provided as criteria parameter.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        string GenerateIf(string segmentId, IEnumerable<ITemplateInputComponent> components, ITemplateInputComponent criteriaParameter, params Predicate<ITemplateInputComponent>[] criteria);

        /// <summary>Writes the given text if the input component matches the specified criteria.</summary>
        /// <param name="text">Text to write.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>The specified text. Empty string if the criterira is not matched.</returns>
        string WriteIf(string text, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria);

        /// <summary>Writes the given text if the input component matches the specified criteria.</summary>
        /// <param name="text">Text to write.</param>
        /// <param name="evaluatedCriteria">An array of flags. All must be <b>true</b> to generate the segment.</param>
        /// <returns>The specified text. Empty string if the criterira is not matched.</returns>
        string WriteIf(string text, params bool[] evaluatedCriteria);

        #endregion

        #region Initialize and Eval methods used by template engine.

        /// <summary>Initializes expression. Implementations should throw exception if the method is call more than once.</summary>
        /// <param name="template">Template to which the current expression belongs to.</param>
        /// <param name="input">Input component.</param>
        void Initialize(ITemplate template, ITemplateInputComponent input);

        /// <summary>Executes expression.</summary>
        /// <returns>Expression result.</returns>
        string Eval();

        #endregion
    }

    #region Documentation.
    /// <summary>Base class for classes compiled in temporary assemblies.</summary>
    /// <remarks><para>Defines properties and methods that may be used in XML template expressions (delimited by $ signs).</para>
    /// <para>C# template expressions have access to the <see cref="Input"/> and <see cref="Variables"/> public properties and
    /// a collection of Generate* and Write* method.</para>
    /// <para>All public types in the System, BizElements.Enteprise may be used in C# template expressions as well as
    /// assemblies explicitely referenced in <b>ReferencedAssemblies</b> element of template's XML file.</para></remarks>
    #endregion
    public abstract class TemplateExpressionBase : ITemplateExpression
    {
        #region Fields.

        Template template;
        bool isInitialized;

        #endregion

        /// <summary>Initializes expression. This method can only be called once.</summary>
        /// <param name="template">Template to which the current expression belongs to. This expression implementation only supports <see cref="Template"/> class.</param>
        /// <param name="input">Input component.</param>
        void ITemplateExpression.Initialize(ITemplate template, ITemplateInputComponent input)
        {
            if (this.isInitialized)
                throw new InvalidOperationException("The expression is already initialized.");

            this.template = (Template)template;
            this.Variables = new TemplateVariableCollection(template.Variables);
            this.Settings = new TemplateSettingCollection(template.Settings);
            this.Input = input;
            this.isInitialized = true;
        }

        /// <summary>Executes compiled expression.</summary>
        /// <returns>Expression result.</returns>
        /// <remarks>Calling this method from XML expression will result with <see cref="StackOverflowException"/>.</remarks>
        public abstract string Eval();

        #region Properties available for usage in XML templates.

        /// <summary>Input component.</summary>
        public ITemplateInputComponent Input { get; private set; }        

        /// <summary>Template variables.</summary>
        public TemplateVariableCollection Variables { get; private set; }

        /// <summary>Template settings.</summary>
        public TemplateSettingCollection Settings { get; private set; }

        /// <summary>Input component variables/metadata.</summary>
        public ComponentVariableSet ComponentVariables
        {
            get { return this.Input.ComponentVariables; }
        }
        
        #endregion        

        #region Generate* and Write* methods available for usage in XML templates.

        /// <summary>Generates the specified template segment for the given input components.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="components">A collection of input components.</param>
        /// <returns>Generated text. Empty string if <b>components</b> collection is empty.</returns>
        public string Generate(string segmentId, IEnumerable<ITemplateInputComponent> components)
        {
            return this.template.Generate(segmentId, components);
        }

        /// <summary>Generates the specified template segment for input components that match the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        public string Generate(string segmentId, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria)
        {
            return this.template.Generate(segmentId, component, componentCriteria);
        }

        /// <summary>Generates the specified template segment for input component if it matches the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        public string GenerateIf(string segmentId, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria)
        {
            return this.template.Generate(segmentId, component, componentCriteria);
        }

        /// <summary>Generates the specified template segment for input component if it matches the specified criteria.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="component">An input component.</param>
        /// <param name="evaluatedComponentCriteria">An array of flags. All must be <b>true</b> to generate the segment.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        public string GenerateIf(string segmentId, ITemplateInputComponent component, params bool[] evaluatedComponentCriteria)
        {
            return this.template.Generate(segmentId, component, evaluatedComponentCriteria);
        }

        /// <summary>Generates the specified template segment for the given input components if the specified condition is met.</summary>
        /// <param name="segmentId">Segment ID as defined in <b>Id</b> attribute of <b>TemplateSegment</b> XML element.</param>
        /// <param name="components">A collection of input components.</param>
        /// <param name="criteriaParameter">A component which is to be passed to the supplied criteria methods.</param>
        /// <param name="criteria">An array of predicates that define a set of criteria for the component provided as criteria parameter.</param>
        /// <returns>Generated text. Empty string if the criterira is not matched.</returns>
        public string GenerateIf(string segmentId, IEnumerable<ITemplateInputComponent> components, ITemplateInputComponent criteriaParameter, params Predicate<ITemplateInputComponent>[] criteria)
        {
            return this.template.GenerateIf(segmentId, components, criteriaParameter, criteria);
        }

        /// <summary>Writes the given text if the input component matches the specified criteria.</summary>
        /// <param name="text">Text to write.</param>
        /// <param name="component">An input component.</param>
        /// <param name="componentCriteria">An array of predicates that define a set of criteria for the input component.</param>
        /// <returns>The specified text. Empty string if the criterira is not matched.</returns>
        public string WriteIf(string text, ITemplateInputComponent component, params Predicate<ITemplateInputComponent>[] componentCriteria)
        { 
            return Template.WriteIf(text, component, componentCriteria);
        }

        /// <summary>Writes the given text if the input component matches the specified criteria.</summary>
        /// <param name="text">Text to write.</param>
        /// <param name="evaluatedCriteria">An array of flags. All must be <b>true</b> to generate the segment.</param>
        /// <returns>The specified text. Empty string if the criterira is not matched.</returns>
        public string WriteIf(string text, params bool[] evaluatedCriteria)
        {
            return Template.WriteIf(text, evaluatedCriteria);
        }

        #endregion
    }
}